package com.bite.ojgateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.bite.ojcommoncore.constants.CacheConstant;
import com.bite.ojcommoncore.constants.HttpConstants;
import com.bite.ojcommoncore.constants.JwtConstants;
import com.bite.ojcommoncore.constants.RateConstants;
import com.bite.ojcommoncore.domain.result.R;
import com.bite.ojcommoncore.enums.ResultCode;
import com.bite.ojcommoncore.utils.JwtUtils;
import com.bite.ojcommoncore.utils.ThreadLocalUtils;
import com.bite.ojcommonredis.service.RedisService;
import com.bite.ojgateway.properties.IgnoreWhiteProperties;

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
    @Autowired
    private IgnoreWhiteProperties ignoreWhiteProperties;
    @Value("jwt.secret")
    private String secret;
    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 过滤器 获取请求路径
        String path = exchange.getRequest().getURI().getPath();
        // 对白名单进行放行
        if (matches(path,ignoreWhiteProperties.getWhites())) {
            return chain.filter(exchange);
        }
        // 对路径进行处理，判断是否登录,获取token
        String token = exchange.getRequest().getHeaders().getFirst(HttpConstants.AUTHENTICATION);

        if (StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange,"请先登录");
        }
        // 对token进行处理， 判断前面是否有Bearer
        if (StrUtil.isNotEmpty(token) && StrUtil.startWith(token,HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX , StrUtil.EMPTY);
        }

        // 判断用户登录信息是否获取
        Claims claims = JwtUtils.parseToken(token, secret);
        Long userId = (Long) claims.get(JwtConstants.LOGIN_USER_ID);

        String tokenKey = CacheConstant.LOGIN_USER_CACHE_KEY + userId;
        String cacheToken = redisService.getCacheObject(tokenKey, String.class);
        if (StrUtil.isEmpty(cacheToken)) {
            // 用户登录过期 , 网关不能抛出异常 --- 进行处理
            return unauthorizedResponse(exchange,"登录过期");
        }
        // 验证用户身份
        String identify = (String) claims.get(JwtConstants.LOGIN_USER_IDENTIFY);
        if (identify == RateConstants.USER && path.contains(HttpConstants.SYSTEM_URL_PREFIX)) {
            return unauthorizedResponse(exchange,"无权访问");
        }
        if (identify == RateConstants.ADMIN && path.contains(HttpConstants.FRIEND_URL_PREFIX)) {
            return unauthorizedResponse(exchange,"无权访问");
        }
        // 用户登录信息没有过期并且身份正确 --
        // TODO 刷新用户token有效期

        return chain.filter(exchange);
    }

    // 过滤器执行优先级 ， 返回值越小优先级越高
    @Override
    public int getOrder() {
        return -200;
    }


    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }
    //拼装webflux模型响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(code, msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    /**
     * 查找指定url是否匹配指定匹配规则链表中的任意一个字符串
     *
     * @param url         指定url
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
        //接口地址如果和白名单中其中一个地址匹配就返回true。 如果便利完白名单中所有的地址都没有匹配的返回false
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则匹配
     * 匹配规则中：
     * pattern 中可以写一些特殊字符
     * ? 表示单个任意字符;
     * * 表示一层路径内的任意字符串，不可跨层级;
     * ** 表示任意层路径;
     *
     * @param pattern 匹配规则
     * @param url     需要匹配的url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }



}
